#ifndef RPC_UTIL_IMPL_H // header guard
#define RPC_UTIL_IMPL_H

#include "rpc_helpers.h"

#include <string>
#include <memory>
#include <NtDsAPI.h>
#include <wincrypt.h>
#include <schannel.h>

namespace _3fd
{
using std::string;

namespace rpc
{
    const char *ToString(ProtocolSequence protSeq);

    const char *ToString(AuthenticationLevel authnLevel);

    const char *ToString(ImpersonationLevel impersonationLevel);

    const char *ConvertAuthnSvcOptToString(unsigned long authnService);

    void AppendSecQosOptsDescription(const RPC_SECURITY_QOS &secQOS, std::ostringstream &oss);

    /// <summary>
    /// RAII for RPC lib C-style strings.
    /// </summary>
    struct RpcString
    {
        RPC_WSTR data;

        RpcString()
            : data(nullptr) {}

        ~RpcString()
        {
            if (data != nullptr)
            {
                CALL_STACK_TRACE;

                LogIfError(
                    RpcStringFreeW(&data),
                    "Failed to release resources of string generated by RPC library",
                    core::Logger::PRIO_CRITICAL
                );
            }
        }
    };

    typedef std::pair<uint32_t, const char*> RpcCodeLabelKVPair;

    /// <summary>
    /// Gathers resources and code to assemble exceptions with
    /// highly detailed information regarding RPC errors.
    /// </summary>
    class RpcErrorHelper
    {
    private:

        static const std::array<const char *, 11> componentMap;

        static const RpcCodeLabelKVPair detectionLocationMap[];

        static const char *GetComponentLabel(uint32_t code);

        static const char *GetDetectionLocationLabel(uint32_t code);

    public:

        static
        core::AppException<std::runtime_error>
        CreateException(
            RPC_STATUS errCode,
            const string &message,
            const string &details
        );
    };

    const unsigned long UUID_VECTOR_MAX_SIZE(32);

    /// <summary>
    /// This is an improvised fix for UUID_VECTOR,
    /// that seems to be wrongly defined in RPC API.
    /// </summary>
    struct UuidVectorFix
    {
        unsigned long size;
        UUID *data[UUID_VECTOR_MAX_SIZE];
    };

    /// <summary>
    /// Simple wrapper for a vector of <see cref="UUID"/> structs.
    /// It uses RAII to guarantee deallocation upon scope end,
    /// not having to resort to smart pointers.
    /// </summary>
    class VectorOfUuids
    {
    private:

        std::vector<UUID *> m_ptrs2Uuids;

    public:

        VectorOfUuids() {}

        VectorOfUuids(VectorOfUuids &&ob)
            : m_ptrs2Uuids(std::move(ob.m_ptrs2Uuids)) {}

        VectorOfUuids &operator =(VectorOfUuids &&ob)
        {
            if (&ob != this)
                m_ptrs2Uuids = std::move(ob.m_ptrs2Uuids);

            return *this;
        }

        ~VectorOfUuids();

        size_t Size() const { return m_ptrs2Uuids.size(); }

        void Add(const UUID &uuid);

        UUID_VECTOR *CopyTo(UuidVectorFix &vec) noexcept;
    };

    /// <summary>
    /// RAII for Directory Service binding handle.
    /// </summary>
    struct DirSvcBinding
    {
        HANDLE handle;

        DirSvcBinding()
            : handle(nullptr) {}

        ~DirSvcBinding()
        {
            if (handle != nullptr)
                DsUnBindW(&handle);
        }
    };

    /// <summary>
    /// RAII for array of SPN's.
    /// </summary>
    struct ArrayOfSpn
    {
        DWORD size;
        LPWSTR *data;

        ArrayOfSpn()
            : size(0), data(nullptr) {}

        ~ArrayOfSpn()
        {
            if (data != nullptr)
                DsFreeSpnArrayW(size, data);
        }
    };

    bool DetectActiveDirectoryServices(DirSvcBinding &dirSvcBinding, bool isClient);

    /// <summary>
    /// Provides access to the system certificate store.
    /// </summary>
    class SystemCertificateStore : notcopiable
    {
    private:

        HCERTSTORE m_certStoreHandle;

    public:

        SystemCertificateStore(DWORD registryLocation, const string &storeName);

        ~SystemCertificateStore();

        PCCERT_CONTEXT FindCertBySubject(const string &certSubject) const;
    };

    /// <summary>
    /// Wraps a credential for secure channel SSP, containing a X.509
    /// certificate from the store and using RAII to control reference
    /// count for the certificate
    /// </summary>
    class SChannelCredWrapper
    {
    private:

        SCHANNEL_CRED m_credStructure;

    public:

        SChannelCredWrapper(
            PCCERT_CONTEXT certCtxtHandle,
            bool strongerSec = false
        );

        SChannelCredWrapper(
            HCERTSTORE certStoreHandle,
            PCCERT_CONTEXT certCtxtHandle,
            bool strongerSec = false
        );

        ~SChannelCredWrapper();

        SCHANNEL_CRED *GetCredential() { return &m_credStructure; }
    };

}// end of namespace rpc
}// end of namespace _3fd

#endif // end of header guard
